<?php

if (!class_exists('UndefinedMethodException')) {

    final class UndefinedMethodException extends Exception {

        public function __construct($class, $method, $args) {
            $this->message = "Undefined method {$method} called on object of class {$class} with arguments [" . implode(' , ', $args) . ']';
        }

    }

}

abstract class AbstractModel {

    private $data = array();

    public function __construct($id = null, $autoLoad = true) {
        $id = intval($id);
        if ($id != 0) {
            $this->setId($id);
            if ($autoLoad) {
                $this->load();
                $this->fromArray($this->toArray());
            }
        }
    }

    public function getValidation($validationName = null) {
        if (!is_null($validationName)) {
            if (isset($this->validation[$validationName])) {
                return $this->validation[$validationName];
            }
        } else {
            return $this->validation;
        }
        return null;
    }

    public function __call($method, $args) {
        switch (substr($method, 0, 3)) {
            case 'get':
                $property = self::upperCaseToUnderline(substr($method, 3));
                if (isset($this->data[$property])) {
                    return $this->data[$property];
                } else {
                    return null;
                }

            case 'set':
                $property = self::upperCaseToUnderline(substr($method, 3));
                $this->data[$property] = $args[0];
                break;

            default:
                throw new UndefinedMethodException(get_called_class(), $method, $args);
        }
    }

    public function fromArray($data) {
        foreach ($data as $key => $value) {
            $methodName = 'set' . self::underlineToUpperCase($key);
            if (method_exists($this, $methodName)) {
                $this->$methodName($value);
            } else {
                $this->data[strtolower($key)] = $value;
            }
        }
    }

    public function toArray() {
        return $this->data;
    }

    public function getEntity() {
        $name = substr(get_called_class(), 0, -5);
        $name = explode('\\', $name);
        $name = end($name);
        return self::upperCaseToUnderline($name);
    }

    public function load($columns = null, SQLCriteria $criteria = null) {
        $sql = new SQLSelect();
        $sql->setEntity($this->getEntity());
        if (!is_null($columns)) {
            if (is_array($columns)) {
                $sql->addColumns($columns);
            } else {
                $sql->addColumn($columns);
            }
        } else {
            $sql->addColumn('*');
        }
        if (!isset($criteria)) {
            $criteria = new SQLCriteria();
            $criteria->add(new SQLFilter('id', '=', $this->data['id']));
        }
        $sql->setCriteria($criteria);

        $conn = SQLTransaction::get();
        $result = $conn->query($sql->getInstruction());
        if ($result != false) {
            $this->fromArray($result);
            return true;
        } else {
            return false;
        }
    }

    public function store() {
        $sql = null;
        if (!isset($this->data['id'])) {
            $sql = new SQLInsert();
            foreach ($this->data as $key => $value) {
                $sql->setRowData($key, $value);
            }
        } else {
            $sql = new SQLUpdate();
            $sql->setEntity($this->getEntity());
            $criteria = new SQLCriteria();
            $criteria->add(new SQLFilter('id', '=', $this->data['id']));
            $sql->setCriteria($criteria);
            foreach ($this->data as $key => $value) {
                if ($key != 'id') {
                    $sql->setRowData($key, $value);
                }
            }
        }
        $conn = SQLTransaction::get();
        return $conn->exec($sql->getInstruction());
    }

    public function delete() {
        $id = $this->data['id'];
        if (isset($id)) {
            $sql = new SQLSelect();
            $sql->setEntity($this->getEntity());

            $criteria = new SQLCriteria();
            $criteria->add(new SQLFilter('id', '=', $id));
            $sql->setCriteria($criteria);

            $conn = SQLTransaction::get();
            return $conn->exec($sql->getInstruction());
        } else {
            return false;
        }
    }

    public static function upperCaseToUnderline($property) {
        $length = strlen($property);
        $property = lcfirst($property);
        for ($i = 1; $i < $length; ++$i) {
            if (strtoupper($property[$i]) == $property[$i]) {
                $property = substr($property, 0, $i) . '_' . lcfirst(substr($property, $i));
            }
        }
        return $property;
    }

    public static function underlineToUpperCase($property) {
        $property = str_replace('_', ' ', $property);
        $property = ucwords(strtolower($property));
        $property = str_replace(' ', '', $property);
        return $property;
    }

}

?>
